package com.ian.controller;

import com.ian.service.ConcurrencyService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.concurrent.DelegatingSecurityContextExecutor;
import org.springframework.security.concurrent.DelegatingSecurityContextRunnable;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.Executor;

/**
 * Integrations with Concurrency
 *
 * @author Witt
 * @version 1.0.0
 * @date 2022/5/10
 */
@RestController
public class ConcurrencyController {

    @Autowired
    private ConcurrencyService concurrencyService;

    @GetMapping("/test1")
    public String test1() {
        return concurrencyService.concurrency();
    }

    /**
     * While very simple, it makes it seamless to transfer the SecurityContext from one Thread to another.
     * This is important since, in most cases, the SecurityContextHolder acts on a per Thread basis.
     * For example, you might have used Spring Security’s <global-method-security> support to secure one of your services.
     * You can now easily transfer the SecurityContext of the current Thread to the Thread that invokes the secured service
     */
    @GetMapping("/concurrency1")
    public void concurrency1() {
        Runnable originalRunnable = () -> {
            /*
             invoke secured service
             我在service层的方法上设置了访问权限注解 “@PreAuthorize("hasRole('admin')")”，该注解可以放在接口方法上，也可以放在相应的实现类的方法上，两者只要放其一即可
             如果没有方法权限，则报错：Exception in thread "Thread-13" org.springframework.security.access.AccessDeniedException: 不允许访问
             */
            concurrencyService.concurrency();
        };

        SecurityContext securityContext = SecurityContextHolder.getContext();
        // DelegatingSecurityContextRunnable 使用了 装饰者模式，装饰了 原始的 Runnable 对象 originalRunnable
        DelegatingSecurityContextRunnable wrappedRunnable = new DelegatingSecurityContextRunnable(originalRunnable, securityContext);

        /*
         Since it is quite common to create a DelegatingSecurityContextRunnable with the SecurityContext
         from the SecurityContextHolder there is a shortcut constructor for it. The following code is the same as the code above:
         */
        DelegatingSecurityContextRunnable wrappedRunnable2 = new DelegatingSecurityContextRunnable(originalRunnable);

        new Thread(wrappedRunnable).start();
    }

    /**
     * The design of DelegatingSecurityContextExecutor is very similar to that of DelegatingSecurityContextRunnable
     *      except it accepts a delegate Executor instead of a delegate Runnable
     */
    @GetMapping("/concurrency2")
    public void concurrency2() {

        SecurityContext emptyContext = SecurityContextHolder.createEmptyContext();
        UsernamePasswordAuthenticationToken authentication
                = new UsernamePasswordAuthenticationToken("user2", "123", AuthorityUtils.createAuthorityList("ROLE_admin"));
        emptyContext.setAuthentication(authentication);

        SimpleAsyncTaskExecutor delegateExecutor = new SimpleAsyncTaskExecutor();

        // DelegatingSecurityContextExecutor 使用了 装饰者模式，装饰了 原始的 SimpleAsyncTaskExecutor 对象 delegateExecutor
        DelegatingSecurityContextExecutor wrappedExecutor = new DelegatingSecurityContextExecutor(delegateExecutor, emptyContext);

        wrappedExecutor.execute(() -> concurrencyService.concurrency());
    }

    @Autowired
    private Executor executor;

    /**
     * How does this shield my code of any knowledge of Spring Security?" Instead of creating the SecurityContext
     * and the DelegatingSecurityContextExecutor in our own code,
     * we can inject an already initialized instance of DelegatingSecurityContextExecutor.
     */
    @GetMapping("/concurrency3")
    public void concurrency3() {
        executor.execute(() -> concurrencyService.concurrency());
    }
}
